home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tricks of the Mac Game Programming Gurus
/
TricksOfTheMacGameProgrammingGurus.iso
/
Book Chapters
/
06 - Audio
/
Example Code
/
Hollywood API Test.c
< prev
next >
Wrap
Text File
|
1995-06-16
|
17KB
|
700 lines
//----------------
// Hollywood API for use with the Macintosh Sound Manager 3.0
//
// This code depends upon Universal Interfaces 2.0a3 or better from Apple Computer, Inc
//
// History
// 1/8/95 Created by Steve Hales
// 3/1/95 Music Architecture added by Jim Nitchals
//----------------
#include <Types.h>
#include <Memory.h>
#include <Quickdraw.h>
#include <OSEvents.h>
#include <Desk.h>
#include <Events.h>
#include <StandardFile.h>
#include <Resources.h>
#include <Windows.h>
#include <Fonts.h>
#include <TextEdit.h>
#include <Menus.h>
#include <Dialogs.h>
#include <ToolUtils.h>
#include <Sound.h>
#include <AIFF.h>
// These includes are for the Apple Music Architecture
#include <QuickTimeComponents.h>
#include <Components.h>
#define TRUE true
#define FALSE false
#include "Hollywood.h"
// Defines and Structures
#define rAboutDialog 128
// Menu defines
#define kMaxMenus 2
#define rAppleMenu 1
#define kAbout 1
#define rFileMenu 2
#define kPlayFile 1
#define kPlaySnd1 2
#define kPlaySnd2 3
#define kPlaySnd3 4
#define kPlaySnd4 5
#define kDoStereoPan 6
#define kSaveAIFF 7
#define kPlayMusicNotes 9
#define kPlayQuickTime 10
#define kQuitApp 12
#define kStereoPanCount 30 // the amount of change during a stereo pan
#define kStereoDelay 4L // amount of ticks to pass before changing the stereo position
#define kPanVoice 1 // which voice to work with durning the pan test
// Examples
#define rExampleSound1 128 // snd examples
#define rExampleSound2 129
#define rExampleRaw1 128 // raw waveform examples
#define rExampleRaw2 129
// Variables
static Boolean appDoneFlag;
static Boolean movieInProgress;
static WindowPtr theMovieDisplayWindow = NULL;
static Movie theMovie;
static char theDate[] = __DATE__;
static char theTime[] = __TIME__;
static MenuHandle Menu[kMaxMenus];
static Boolean processStereoPan;
static short int stereoPos, stereoDirection;
static long lastProcessTick;
// Functions
static void StartQuickTimeMovie(void);
static void ProcessQuickTimeMovie(void);
static void EndQuickTimeMovie(void);
static Boolean GetReadFileName(char *pName, short *pVRef, OSType *pType, short typeCount)
{
static Point anchor = {100, 7}; /* top-left */
SFReply reply;
SFGetFile(anchor, NULL, NULL, typeCount, pType, NULL, &reply);
if (pType)
{
pType[0] = reply.fType;
}
BlockMove((void *)reply.fName, pName, 63L);
*pVRef = reply.vRefNum;
return(reply.good);
}
static Boolean GetWriteFileName(void *pName, short int *pVRef, void *pSaveText)
{
static Point anchor = {50, 105}; /* top-left */
SFReply reply;
SFPutFile(anchor, (ConstStr255Param)pSaveText, (ConstStr255Param)pName, NULL, &reply);
if (reply.good == FALSE)
{
return(FALSE); /* user pressed CANCEL */
}
*pVRef = reply.vRefNum;
BlockMove(reply.fName, pName, 63L);
return(TRUE);
}
static void DisplayAboutBox(void)
{
DialogRecord dRecord;
GrafPtr savePort;
register DialogPtr theDialog;
short theItem;
GetPort(&savePort);
theDialog = GetNewDialog(rAboutDialog, &dRecord, (WindowPtr)-1L);
SetPort(theDialog);
ModalDialog(NULL, &theItem);
CloseDialog(theDialog); // I supplied memory for GetNewDialog
SetPort(savePort);
}
static void StartQuickTimeMovie(void)
{
SFTypeList theTypeList;
StandardFileReply theReply;
short int myRef;
Rect wRect;
FSSpec theMidiFile;
OSErr theErr;
if (movieInProgress)
{
EndQuickTimeMovie();
movieInProgress = FALSE;
}
/* Let the user select a movie */
theTypeList[0] = MovieFileType;
theTypeList[1] = 'Midi';
StandardGetFile(NULL, 2, theTypeList, &theReply);
if (theReply.sfGood)
{
/* initialize movie toolbox */
theErr = EnterMovies();
if (theErr == noErr)
{
if (theReply.sfType == 'Midi')
{ // Then we are converting a Midi file into a QuickTime movie
theMidiFile = theReply.sfFile;
StandardPutFile(NULL, theReply.sfFile.name, &theReply);
if (theReply.sfGood)
{
theErr = ConvertFileToMovieFile(&theMidiFile, &theReply.sfFile, 'TVOD', 0,
NULL, showUserSettingsDialog, 0L, NULL, 0L);
}
else
{
theErr = badFormat;
}
}
/* open the movie file, get the movie from it, then close it */
if ( (theErr == noErr) && (OpenMovieFile(&theReply.sfFile, &myRef, fsRdPerm) == noErr) )
{
if (NewMovieFromFile(&theMovie, myRef, NULL, NULL, newMovieActive, NULL) == noErr)
{
/* Find out the size of the movie. */
GetMovieBox (theMovie, &wRect);
/* If the movie has no video, don't display any window. */
if (wRect.bottom)
{
wRect.top += 48;
wRect.left += 48;
wRect.bottom += 48;
wRect.right += 48;
theMovieDisplayWindow = NewCWindow (NULL, &wRect, "\pMovie Window", TRUE, plainDBox,
(WindowPtr) -1L, TRUE, 0);
SetMovieGWorld (theMovie, (CGrafPtr) theMovieDisplayWindow, 0);
}
/* Tell QuickTime we're playing from the beginning. */
GoToBeginningOfMovie(theMovie);
/* PrerollMovie helps QuickTime pre-load parts of the movie to
give smoother playback.
Second parameter is the time we expect to start playing at.
Third parameter is the playback rate we expect to be using.
1.0 in fixed rate. */
PrerollMovie(theMovie, 0, 0x10000);
/* Start playing forward. */
StartMovie(theMovie);
movieInProgress = TRUE;
}
CloseMovieFile(myRef);
}
}
}
}
static void ProcessQuickTimeMovie(void)
{
if (movieInProgress)
{
if (IsMovieDone(theMovie))
{
EndQuickTimeMovie();
}
else
{
/* Your event loop should call MoviesTask very regularly for smooth playback.
The second parameter to MoviesTask is the maximum number of milliseconds
QuickTime may use when playing multiple movies. Pass 0 to allow QuickTime
to service every movie properly. */
MoviesTask(theMovie, 0);
}
}
}
static void EndQuickTimeMovie(void)
{
if (movieInProgress)
{
/* Throw away the window if there was one. */
if (theMovieDisplayWindow)
{
DisposeWindow(theMovieDisplayWindow);
theMovieDisplayWindow = NULL;
}
/* Make this call to free up resources used by QuickTime and the movie. */
DisposeMovie(theMovie);
movieInProgress = FALSE;
ExitMovies();
}
}
static void PlayQuickTimeNotes(void)
{
NoteAllocator theNoteAllocator; /* The music note allocator component */
NoteChannel myNoteChannel; /* A channel to play notes through */
NoteRequest myNoteRequest; /* A structure describing the instrument we want to play */
short int i, velocity, instrument_index;
unsigned long current_tick_count;
/* This is a list of General MIDI instrument numbers we want to play. */
static int instrument_list[6] = {1, 5, 7, 8, 12, 13};
/* Open the note allocator component */
theNoteAllocator = OpenDefaultComponent(kNoteAllocatorType, 0);
/* Walk through our list of available instruments. */
for (instrument_index = 0; instrument_index < 6; instrument_index++)
{
/* Set up a NoteRequest structure with a request for a piano with polyphony of 1. */
myNoteRequest.polyphony = 1;
myNoteRequest.typicalPolyphony = 1<<16;
/* We can request a specific instrument by name. If it's not found, the gmNumber field
provides the General MIDI fallback instrument. */
myNoteRequest.tone.synthesizerType = 0;
myNoteRequest.tone.synthesizerName[0] = 0;
myNoteRequest.tone.instrumentName[0] = 0;
myNoteRequest.tone.instrumentNumber = 0;
myNoteRequest.tone.gmNumber = instrument_list[instrument_index];
/* Allocate a note channel with our desired instrument as filled out in the NoteRequest */
NANewNoteChannel(theNoteAllocator, &myNoteRequest, &myNoteChannel);
/* Load the resources needed by the instrument */
NAPrerollNoteChannel(theNoteAllocator, myNoteChannel);
/* Perform a loop, playing notes at increasing pitch and velocity, using TickCount()
as a timebase. You can play notes at interrupt time, so you can schedule note
events with the Time Manager, or through your own schemes. */
current_tick_count = TickCount() + 6;
for (i = 32; i < 90 && Button() == false; i += 3)
{
velocity = i + 10;
/* Play the Note */
NAPlayNote(theNoteAllocator, myNoteChannel, i, velocity);
/* Wait awhile */
while (current_tick_count > TickCount()) {};
current_tick_count += 6; /* Prepare to do this again in another 1/10th-second. */
/* Stop playing the note. */
NAPlayNote(theNoteAllocator, myNoteChannel, i, 0);
}
/* Wait for the last note to decay away to silence. */
current_tick_count += 20;
while (current_tick_count > TickCount()) {};
/* Dispose the note channel when we're done with it. */
NADisposeNoteChannel(theNoteAllocator, myNoteChannel);
}
CloseComponent(theNoteAllocator);
}
static void FilePlayCallback(short int voiceNumber, short int what, long userData)
{
}
static void PlayFile(void)
{
OSType types[2];
FSSpec theFile;
OSErr theErr;
types[0] = AIFFID; // from AIFF.h
types[1] = AIFCID; // from AIFF.h
if (GetReadFileName((char *)theFile.name, &theFile.vRefNum, types, 2))
{
theFile.parID = 0;
theErr = HY_StartFilePlay(kPanVoice, &theFile, FilePlayCallback, 0, 40000L, TRUE);
}
}
static void PlaySoundEffect1(void)
{
static SndReference theSound1 = NULL;
if (theSound1 == NULL) // only load this sample in once
{
theSound1 = HY_GetSoundResource(rExampleSound1);
}
if (theSound1)
{
HY_PlaySoundHandle(0, theSound1, NULL, 0, TRUE);
}
}
static void PlaySoundEffect2(void)
{
static SndReference theSound2 = NULL;
if (theSound2 == NULL) // only load this sample in once
{
theSound2 = HY_GetSoundResource(rExampleSound2);
}
if (theSound2)
{
HY_PlaySoundHandle(kPanVoice, theSound2, NULL, 0, TRUE);
}
}
static void PlaySoundEffect3(void)
{
Handle theSample, theNewSample;
Handle theCompressedSample;
theSample = GetResource('wave', rExampleRaw1);
if (theSample)
{
HLock(theSample);
// our raw waveform is mono 8 bit, and should be played back at 11 kHz.
theNewSample = HY_CreateSndResourceFromPtr(*theSample, GetHandleSize(theSample), rate11khz,
8, 1, kMiddleC);
HUnlock(theSample);
if (theNewSample)
{
HY_RegisterThisSound(theNewSample);
HY_PlaySoundHandle(0, theNewSample, NULL, 0L, TRUE);
while (HY_IsVoiceEmpty(0) == FALSE)
{
HY_ServiceTasks(); // at least call once in a while
}
HY_UnregisterSoundResource(theNewSample);
DisposeHandle(theNewSample);
}
// now MACE compress it, and play it back.
HLock(theSample);
// 6 to 1 compression is pretty bad in this case, but 3 to 1 is ok
theCompressedSample = HY_CreateMACESndResourceFromPtr(sixToOne, *theSample, GetHandleSize(theSample),
rate11khz, 8, 1, kMiddleC);
HUnlock(theSample);
ReleaseResource(theSample);
if (theCompressedSample)
{
HY_RegisterThisSound(theCompressedSample);
HY_PlaySoundHandle(0, theCompressedSample, NULL, 0L, TRUE);
while (HY_IsVoiceEmpty(0) == FALSE)
{
HY_ServiceTasks(); // at least call once in a while
}
HY_UnregisterSoundResource(theCompressedSample);
DisposeHandle(theCompressedSample);
}
}
}
static void PlaySoundEffect4(void)
{
Handle theSample;
Handle theNewSample;
theSample = GetResource('wave', rExampleRaw2);
if (theSample)
{
HLock(theSample);
// our raw waveform is stereo 8 bit, and should be played back at 14321 kHz.
theNewSample = HY_CreateSndResourceFromPtr(*theSample, GetHandleSize(theSample), X2Fix(20000.0),
8, 2, 60);
HUnlock(theSample);
ReleaseResource(theSample);
if (theNewSample)
{
HY_RegisterThisSound(theNewSample);
HY_PlaySoundHandle(0, theNewSample, NULL, 0L, TRUE);
while (HY_IsVoiceEmpty(0) == FALSE)
{
HY_ServiceTasks(); // at least call once in a while
}
HY_UnregisterSoundResource(theNewSample);
DisposeHandle(theNewSample);
}
}
}
static void PhaseWaveform(register char *p, register long size)
{
register char *ep;
ep = p + size;
while (p < ep)
{
*p -= 0x80;
p++;
}
}
static void SaveAIFFFile(void)
{
Handle theSample;
FSSpec theFile;
OSErr theErr;
long length;
theFile.name[0] = 0;
if (GetWriteFileName(theFile.name, &theFile.vRefNum, "\pSave Example AIFF…"))
{
theFile.parID = 0;
theSample = GetResource('wave', rExampleRaw1);
if (theSample)
{
DetachResource(theSample);
HLock(theSample);
length = GetHandleSize(theSample);
// AIFF files expect the volume levels to be in the range of -128 to 127 for 8 bit data, and
// -32768 to 32767. Where zero is silence. The standard Macintosh 8 bit resource silence is 128,
// so we have to phase the sample back.
PhaseWaveform(*theSample, length);
theErr = HY_CreateAIFFFileFromPtr(&theFile, *theSample, length,
rate11khz,
8, 1);
HUnlock(theSample);
DisposeHandle(theSample);
}
}
}
static void ProcessStereoPan(short int voiceNumber)
{
if (TickCount() > lastProcessTick)
{
lastProcessTick = TickCount() + kStereoDelay;
HY_SetStereoPosition(voiceNumber, stereoPos);
stereoPos += stereoDirection;
if (stereoPos < kFullLeft)
{
stereoDirection = kStereoPanCount;
stereoPos += stereoDirection;
}
if (stereoPos > kFullRight)
{
stereoDirection = -kStereoPanCount;
stereoPos += stereoDirection;
}
}
}
static void ProcessMenuCommands(long menuSelection)
{
short int theMenuSelection, theItemSelection;
Str255 deskName;
GrafPtr savePort;
theMenuSelection = HiWord(menuSelection);
theItemSelection = LoWord(menuSelection);
switch (theMenuSelection)
{
case rAppleMenu:
switch (theItemSelection)
{
case kAbout:
DisplayAboutBox();
break;
default:
GetItem(Menu[0], theItemSelection, deskName);
GetPort(&savePort);
OpenDeskAcc(deskName);
SetPort(savePort);
break;
}
break;
case rFileMenu:
switch (theItemSelection)
{
case kPlayFile:
PlayFile();
break;
case kPlaySnd1:
PlaySoundEffect1();
break;
case kPlaySnd2:
PlaySoundEffect2();
break;
case kPlaySnd3:
PlaySoundEffect3();
break;
case kPlaySnd4:
PlaySoundEffect4();
break;
case kDoStereoPan:
processStereoPan = TRUE;
stereoPos = kMiddle;
HY_SetStereoPosition(kPanVoice, stereoPos);
stereoDirection = -kStereoPanCount; // start left
lastProcessTick = TickCount() + kStereoDelay;
break;
case kSaveAIFF:
SaveAIFFFile();
break;
case kPlayMusicNotes:
PlayQuickTimeNotes();
break;
case kPlayQuickTime:
StartQuickTimeMovie();
break;
case kQuitApp:
appDoneFlag = TRUE;
break;
}
break;
}
HiliteMenu(0);
}
void main()
{
EventRecord theEvents;
WindowPtr theWindow;
OSErr theErr;
short int count;
// NOTE: The order of initilizing the system seems very important when running under
// MultFinder!!!
#if THINK_C
InitGraf(&thePort); // set up screen port
#else
InitGraf(&qd.thePort);
#endif
InitFonts(); // set up font manager
InitWindows(); // set up window stuff
InitMenus(); // set up menu mgr
TEInit(); // set up text editor for system use
InitDialogs(NULL); // set up Dialog stuff
InitCursor(); // put pointer shape in mouse
MaxApplZone(); // force expansion
FlushEvents(everyEvent, 0); // clear all events
MoreMasters();
MoreMasters();
// Setup menus
Menu[0] = GetMenu(rAppleMenu);
AddResMenu(Menu[0], 'DRVR');
Menu[1] = GetMenu(rFileMenu);
for (count = 0; count < kMaxMenus; count++)
{
if (Menu[count])
{
InsertMenu(Menu[count], 0);
}
}
DrawMenuBar();
theErr = HY_Setup(kUseStereo, 3);
appDoneFlag = FALSE;
processStereoPan = FALSE;
movieInProgress = FALSE;
while (appDoneFlag == FALSE)
{
HY_ServiceTasks();
ProcessQuickTimeMovie();
if (processStereoPan)
{
ProcessStereoPan(kPanVoice);
}
if (WaitNextEvent(everyEvent, &theEvents, 0L, NULL))
{
switch(theEvents.what)
{
case mouseDown:
switch (FindWindow(theEvents.where, &theWindow))
{
case inSysWindow:
SystemClick(&theEvents, theWindow);
break;
case inMenuBar:
ProcessMenuCommands(MenuSelect(theEvents.where));
break;
}
break;
case app4Evt: // Suspend/Resume Events
if ( ((theEvents.message >> 24L) & 0x00FF) == 0x01)
{
if (theEvents.message & 1) // message field contains flags
{
// HY_ResumeHardware();
}
else
{
// suspend
// HY_PauseHardware();
}
}
break;
case autoKey:
case keyDown:
count = theEvents.message & 0xFF;
if (theEvents.modifiers & cmdKey)
{
ProcessMenuCommands(MenuKey(count));
}
else
{
switch(count)
{
case 'q':
appDoneFlag = TRUE;
break;
}
}
break;
}
}
}
// release menus
for (count = 0; count < kMaxMenus; count++)
{
if (Menu[count])
{
ReleaseResource((Handle)Menu[count]);
}
}
HY_UnregisterAllSoundResources();
HY_Cleanup();
EndQuickTimeMovie();
FlushEvents(everyEvent, 0); // clear all events
}
// EOF of Hollywood API Test.c